package com.studiofortress.sf.util.collections;

import java.util.Set;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

/**
 * This is a hash map of Class to Value. Why use this instead of a normal
 * map\<? extends Super\>? Because it does extra work in order to take into
 * account of class inheritence. So you can get all values which are the child a
 * given class.
 *
 * @author Joseph Lenton - JL235@Kent.ac.uk
 */
public class ClassInheritenceMap<Super, Value>
{
    private Map<Class<? extends Super>, Value> classValues;
    private Map<Class<? extends Super>, ArrayList<Class<? extends Super>>> keyClassSubClasses;
    
    public ClassInheritenceMap()
    {
        classValues = new CachingHashMap<Class<? extends Super>, Value>();
        keyClassSubClasses = new CachingHashMap<
                Class<? extends Super>,
                ArrayList<Class<? extends Super>>>();
    }
    
    public Value put(Class<? extends Super> key, Value value)
    {
        ensureKeySubClasses( key );
        return classValues.put(key, value);
    }

    public Value remove(Class<? extends Super> key)
    {
        return classValues.remove(key);
    }

    public Collection<Value> values()
    {
        return classValues.values();
    }
    
    public Value get(Class<? extends Super> key)
    {
        ensureKeySubClasses( key );
        return classValues.get(key);
    }

    /**
     * Iterates over all of values stored against the given class, and it's
     * sub-classes.
     * @param superClass The class to iterate against.
     * @param iterator The iterator to call for each value this map has stored.
     */
    public void iterateSubKeys(final Class<? extends Super> superClass, final CallbackIterator<Value> iterator)
    {
        ensureKeySubClasses( superClass );
        
        final ArrayList<Class<? extends Super>> classes = keyClassSubClasses.get( superClass );
        final int classesSize = classes.size();
        
        for (int i = 0; i < classesSize; ++i) {
            iterator.iterate( classValues.get(classes.get(i)) );
        }
    }

    /**
     * @param superClass
     * @return A list containing all the values stored under the given class or any of it's sub-classes.
     */
    public ArrayList<Value> getSubKeys(Class<? extends Super> superClass)
    {
        ensureKeySubClasses( superClass );

        final ArrayList<Value> inheritedClasses = new ArrayList<Value>();
        final ArrayList<Class<? extends Super>> classes = keyClassSubClasses.get( superClass );
        final int classesSize = classes.size();

        for (int i = 0; i < classesSize; ++i) {
            inheritedClasses.add( classValues.get(classes.get(i)) );
        }

        return inheritedClasses;
    }

    public boolean isEmpty()
    {
        return size() == 0;
    }
    
    public int size()
    {
        return classValues.size();
    }

    /**
     *
     * @return
     */
    public Set<Class<? extends Super>> getKeys()
    {
        return classValues.keySet();
    }
    
    public ArrayList<Class<? extends Super>> getSubClasses(Class<? extends Super> superClass)
    {
        ensureKeySubClasses( superClass );
        return keyClassSubClasses.get( superClass );
    }
    
    private void ensureKeySubClasses(Class<? extends Super> superClass)
    {
        if ( !keyClassSubClasses.containsKey( superClass ) ) {
            generateKeySubClasses(superClass);
        }
    }
    
    private void generateKeySubClasses(Class<? extends Super> newKlass)
    {
        ArrayList<Class<? extends Super>> inheritedClasses = new ArrayList<Class<? extends Super>>();
        inheritedClasses.add( newKlass );

        for (Class<? extends Super> existingKlass : keyClassSubClasses.keySet()) {
            if (isInherited(newKlass, existingKlass)) {
                inheritedClasses.add(existingKlass);
            }
            if (isInherited(existingKlass, newKlass)) {
                keyClassSubClasses.get(existingKlass).add(newKlass);
            }
        }
        
        keyClassSubClasses.put( newKlass, inheritedClasses );
    }
    
    private boolean isInherited(Class<? extends Super> superKlass, Class<? extends Super> subKlass)
    {
        return superKlass.isAssignableFrom(subKlass);
    }
}
